<?php
/*
 * Created on 08.01.2007
 *
 */

define("PSP_SKIP_PAGE", 1);
define("PSP_SKIP_BODY", 2);
define("PSP_EVAL_BODY_BUFFERED", 3);
define("PSP_EVAL_BODY_INCLUDE", 4);
define("PSP_EVAL_BODY_AGAIN", 5);

/*
 * Empty tag without body (ignore body if any) 
 */
define("PSP_EMPTY_TAG", 1);
/*
 * Body tag; body passed to tag
 */
define("PSP_BODY_TAG", 2);
/*
 * Iteration tag which may iterate through the body more than one time
 */
define("PSP_ITERATION_TAG", 3);

class phpinchains_psp_Tag {

    /**
     * Is a body tag (if false, body is ignored)
     * @var int
     */
    var $tagType = PSP_EMPTY_TAG;
    /**
     * Tag name in form namespace:tagname
     * @var string
     */
    var $tagName   = '';
    /**
     * Variable name for instance
     * @var string
     */
    var $varname;
    /**
     * Variable name for doStartTag return value
     * @var string
     */
    var $varRname;
    /**
     * Listener with body (if body tag)
     * @var phpinchains_psp_OutputListener
     */
    var $body;
    /**
     * Output writer
     * @var phpinchains_psp_OutputWriter
     */
    var $out;
    /**
     * Action form (scope)
     * @var phpinchains_ActionForm
     */
    var $actionform;
    /**
     * Reference to the PSP the tag is used in
     */
    var $page;
    /**
     * Set parent
     * @var phpinchains_psp_Tag
     */
    var $parent;
    /**
     * required attributes
     */
    var $requiredAttributes = array();
    /**
     * allowed attributes
     */
    var $attributes = array();
    /**
     * attributes which produced errors
     */
    var $errorAttributes = array();
    /**
     * did occur errors with attributes?
     * @var bool
     */
    var $hasAttributeErrors = false;

    /**
     * Find nearest direct or indirect parent of the given class
     * 
     * @param phpinchains_psp_Tag       $from        Tag instance to start with
     * @param string                    $classname   Classname of the instance to find
     */
    function findAncestorWithClass(phpinchains_psp_Tag $from, $classname)
    {
        $parent = & $from->getParent();
        if (!$parent)
            return null;
        if (strtolower(get_class($parent)) == strtolower($classname))
            return $parent;
        else
            return $parent->findAncestorWithClass($parent, $classname);
    }

    function setOutputWriter(phpinchains_psp_OutputWriter $out)
    {
        $this->out = $out;
    }

    function setActionForm(& $actionform)
    {
        $this->actionform = &$actionform;
    }

    function setPage(& $page)
    {
        $this->page = &$page;
    }

    function getBodyListener ()
    {
        $this->body = new phpinchains_psp_OutputListener();
        return $this->body;
    }
    
    /**
     * Attribute setting function
     * makes processing of EL possible (lamba function)
     */
    function setAttributeFunction($func)
    {
        $this->attrsfunc = $func;
    }

    function setAttribute($name, $value)
    {
        $this->errorAttributes[$name] = false;
    	if (isset($this->requiredAttributes[$name])
    		&& (($this->requiredAttributes[$name] == '*' && empty($value))
    			|| ($this->requiredAttributes[$name] != '*'
    				&& (is_array($value)
    					|| !preg_match('/^'.$this->requiredAttributes[$name].'$/', $value))
    				)))
    	{
    		$this->out->getRootListener()->write("<b>Attribute value of attribute '$name' of tag $this->tagName does not match specification at line '".$this->attrs['__line__']."' in file '".$this->attrs['__file__']."'!</b><br/>");
    		$this->errorAttributes[$name] = true;
    		$this->hasAttributeErrors = true;
    	}
        $this->attrs[$name] = $value;
    }
    
    function getAttribute($name)
    {
    	if (isset($this->attrs[$name]))
        	return $this->attrs[$name];
    }
    
    function setParent(phpinchains_psp_Tag $parent)
    {
        $this->parent = $parent;
    }

    /**
     * Get the direct parent tag
     *
     * @return phpinchains_psp_Tag Parent tag
     */
    function getParent()
    {
        return $this->parent;
    }
    
    /**
     * Checks for required atributes
     */
    function checkRequiredAttributes()
    {
    	foreach ($this->requiredAttributes as $n => $v)
    	{
    		if (!isset($this->attrs[$n]))
    		{
    			$this->errorAttributes[$n] = true;
    			$this->hasAttributeErrors  = true;
    			$this->out->getRootListener()->write("<b>Attribute $n of tag $this->tagName is required at line ".$this->attrs['__line__']." in file ".$this->attrs['__file__']."!</b><br/>");
    		}
    	}
    }

    protected function reportError ($msg) {
        $this->out->getRootListener()->write("<strong>Error in $this->tagName at line ".$this->attrs['__line__']." in file ".$this->attrs['__file__'].":</strong> $msg");
    }
    
    /**
     * Call attribute setting function and doStartTag
     */
    function _doStartTag()
    {
        $func = $this->attrsfunc;
        $ctx = & phpinchains_ApplicationContext::getInstance();
        $func($this->actionform, $ctx->getEnv(), $this);
        $this->checkRequiredAttributes();
        return $this->doStartTag();
    }

    /**
     * Refresh attributes and call doAfterBody
     */
    function _doAfterBody()
    {
        $func = $this->attrsfunc;
        $ctx = & phpinchains_ApplicationContext::getInstance();
        $func($this->actionform, $ctx->getEnv(), $this);
        return $this->doAfterBody();
    }

    function genRandomString() {
        $length = 8;
        $characters = '0123456789abcdefghijklmnopqrstuvwxyz';
        $string = '';

        for ($p = 0; $p < $length; $p++) {
            $string .= $characters[mt_rand(0, strlen($characters))];
        }

        return $string;
    }

    function startElement (&$sender, $attrs, $line, $file = '')
    {
        $name = split(":", $this->tagName);
        $random_name = $this->genRandomString();
        $parent = $sender->topParentVarname();
        $this->varname  = $var  = '$_psp_'.join('_', $sender->tagstack).'_'.$random_name.'_'.$sender->getTagCount($this->tagName);
        $sender->pushParentVarname($var);
        $this->varRname = $varR = '$_psp_ret_'.join('_', $sender->tagstack).'_'.$random_name.'_'.$sender->getTagCount($this->tagName);
        $sender->incTagCount($this->tagName);
        $funcode = '$tag->setAttribute("__line__", '.$line.');$tag->setAttribute("__file__", "'.$file.'");';
        foreach ($attrs as $n => $v)
        {
            $funcode .= '$tag->setAttribute("'.$n.'", '.$v.');';
        }
        $code = '            try {'."\n";
        $code .= '            '.$var.' = & $this->getTagInstance("'.$name[0].'", "'.$name[1].'");'."\n"
                    .'            '.$var.'->setOutputWriter($out);'."\n"
                    .'            '.$var.'->setPage($this);'."\n";
        if ($parent)
            $code .= '            '.$var.'->setParent('.$parent.');'."\n";
        $code .= '            '.$var.'->setActionForm($actionform);'."\n"
                    .'            '.$var.'->setAttributeFunction(create_function("&\$actionform, &\$env, &\$tag", \''.str_replace("'", "\'", $funcode).'\'));'."\n"
                    .'            '.$varR.' = '.$var.'->_doStartTag();'."\n";
        if ($this->tagType > PSP_EMPTY_TAG)
        {
             $code .= '            if ('.$varR.' != PSP_SKIP_BODY) {
                if ('.$varR.' != PSP_EVAL_BODY_INCLUDE) {
                    $out->pushListener('.$var.'->getBodyListener());
                    '.$var.'->doInitBody();
                }'."\n";
             if ($this->tagType == PSP_ITERATION_TAG)
                $code .= '                do {'."\n";
        }
        $sender->appendCode($code);
    }
    
    function endElement (&$sender)
    {
        $code = '';
        if ($this->tagType > PSP_EMPTY_TAG)
        {
            if ($this->tagType == PSP_ITERATION_TAG)
            {
                $code = '                   if ('.$this->varname.'->_doAfterBody() != PSP_EVAL_BODY_AGAIN)
                       break;
               } while (true);'."\n";
            }
            $code .= '               if ('.$this->varRname.' != PSP_EVAL_BODY_INCLUDE)
               {
                   $out->popListener();
               }
            }'."\n";
        }
        $code .= '            if ('.$this->varname.'->doEndTag() == PSP_SKIP_PAGE) return $out;'."\n";
        $code .= '            } catch (Exception $e) {
                $out->write("<b>".$e->getMessage()."</b><br/><pre>".$e->getTraceAsString()."</pre>");
            }'."\n";
        $sender->popParentVarname();
        $sender->appendCode($code);
    }
    
    /** methods to override **/
    
    /**
     * parent may be of tag
     * if empty array returned, all tags are allowed
     */
    function parentIn() {
    	return array();
    }
    
    function doInitBody()
    {
        
    }
    
    function doAfterBody()
    {
        return PSP_SKIP_BODY;
    }
    
    function doStartTag()
    {
        return PSP_SKIP_BODY;
    }
    
    function doEndTag()
    {
        return 0;
    }
    
}
?>